package finn.movingheadtracker;

import java.awt.Dimension;
import java.io.File;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Iterator;
import java.util.List;

import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;
import javax.xml.transform.Transformer;
import javax.xml.transform.TransformerConfigurationException;
import javax.xml.transform.TransformerException;
import javax.xml.transform.TransformerFactory;
import javax.xml.transform.dom.DOMSource;
import javax.xml.transform.stream.StreamResult;

import org.w3c.dom.*;

import finn.movingheadtracker.MergedUniverse.ChannelMergeMode;

public class Configuration {
	private static String defPath = "src/tracker.xml";
	public static Cameraviews views;
	public Configuration(String path) {
		Configuration.load(path);
	}
	public Configuration() {
		Configuration.load(defPath);
	}
	
	//TODO Exceptions if file is not valid
	private static void load(String path){
		try {
			File fXmlFile = new File(path);
			DocumentBuilderFactory dbFactory = DocumentBuilderFactory.newInstance();
			DocumentBuilder dBuilder = dbFactory.newDocumentBuilder();
			Document doc = dBuilder.parse(fXmlFile);
 
			//optional, but recommended
			//read this - http://stackoverflow.com/questions/13786607/normalization-in-dom-parsing-with-java-how-does-it-work
			doc.getDocumentElement().normalize();
			
			/*
			 * Parse Cameraviews
			 */
			views = new Cameraviews();
			Node ecameraviews = doc.getElementsByTagName("cameraviews").item(0);
			NodeList cameraviews = ecameraviews.getChildNodes();
			for(int i = 0; i< cameraviews.getLength();i++){
				Cameraview view = new Cameraview();
				Node node = cameraviews.item(i);
				if (node.getNodeType() == Node.ELEMENT_NODE) {
			        Element eview = (Element) node;
					if (node.getNodeName()=="camera") {
						view.id = Integer.parseInt(eview.getAttribute("id"));
						view.name = eview.getElementsByTagName("name").item(0)
								.getTextContent();
						view.setSource(eview.getElementsByTagName("sourcetype")
								.item(0).getTextContent(), eview
								.getElementsByTagName("source").item(0)
								.getTextContent());
						view.res = new Dimension(
								Integer.parseInt(((Element) eview
										.getElementsByTagName("resolution")
										.item(0)).getAttribute("y")),
								Integer.parseInt(((Element) eview
										.getElementsByTagName("resolution")
										.item(0)).getAttribute("x")));
						view.used = Boolean.parseBoolean(eview.getElementsByTagName("used").item(0).getTextContent());
						views.addElement(view);
					}
					if (node.getNodeName()=="freeid") {
						views.nextId = Integer.parseInt(eview.getTextContent());
					}
			    }
			}
			//End parsing cameraviews
			

			/*
			 * Parse Universes
			 */
			Node euniverses = doc.getElementsByTagName("universes").item(0);
			NodeList universes = euniverses.getChildNodes();
			for(int i = 0; i< universes.getLength();i++){
				Node node = universes.item(i);
				if(node.getNodeType() == Node.ELEMENT_NODE){
					Element euniverse = (Element)node;
					switch(euniverse.getAttribute("type")){
					case "artnet":
						short direction;
						if(euniverse.getElementsByTagName("direction").item(0).getTextContent().equals("IN")){
							direction = Universe.IN;
						}
						else{
							direction = Universe.OUT;
						}
						UniverseArtnet un = new UniverseArtnet(Integer.parseInt(euniverse.getAttribute("id")),euniverse.getElementsByTagName("name").item(0).getTextContent(),direction,Universe.ARTNET);
						un.ArtnetUniverseId = Integer.parseInt(euniverse.getElementsByTagName("universe").item(0).getTextContent());
						un.SubnetUniverseId = Integer.parseInt(euniverse.getElementsByTagName("subnet").item(0).getTextContent());
						DmxMerger.universes.add(un);
					}
				}
			}
			//End parsing universes
			
			/*
			 * Parse MergedUniverses
			 */
			Node emuniverses = doc.getElementsByTagName("muniverses").item(0);
			NodeList muniverses = emuniverses.getChildNodes();
			List<MergedUniverse> muns =  new ArrayList<MergedUniverse>();
			//do this loop for every mergeduniverse
			int MUid = 1;
			for(int i = 0; i< muniverses.getLength();i++){
				Node node = muniverses.item(i);
				if(node.getNodeType() == Node.ELEMENT_NODE){
					Element emuniverse = (Element)node;
					MergedUniverse mun = new MergedUniverse(MUid);
					MUid++;
					//set inputs
					String[] str_in = emuniverse.getAttribute("input").split(",");
					int[] int_in = new int[str_in.length];
					for(int j = 0; j < str_in.length; j++){
						int_in[j] = Integer.parseInt(str_in[j]);
					}
					//set outputs
					String[] str_out = emuniverse.getAttribute("output").split(",");
					int[] int_out = new int[str_out.length];
					for(int j = 0; j < str_out.length; j++){
						int_out[j] = Integer.parseInt(str_out[j]);
					}
					mun.input = int_in;
					mun.output = int_out;
					ChannelMergeMode[] cmm = new ChannelMergeMode[512];
					NodeList channels = emuniverse.getChildNodes();
					//do this loop for every channels to set ChannelmergeModes
					for(int j = 0; j < channels.getLength();j++){
						Node channel_node = channels.item(j);
						if(channel_node.getNodeType() == Node.ELEMENT_NODE){
							Element echannel = (Element)channel_node;
							ChannelMergeMode cm = mun.new ChannelMergeMode();
							//set for every universe.cmm the appropriate ChannelMergeMode
							cm = mun.new ChannelMergeMode();
							if(echannel.getAttribute("type").equals("LTP")){
								cm.type = ChannelMergeMode.LTP;
							}
							else{
								cm.type = ChannelMergeMode.HTP;
							}
							String[] str_un = echannel.getAttribute("primary").split(",");
							int[] int_un = new int[str_un.length];
							for(int l = 0; l < str_un.length; l++){
								int_un[l] = Integer.parseInt(str_un[l]);
							}
							cm.universes = int_un;
							for(int k = Integer.parseInt(echannel.getAttribute("from")); k <= Integer.parseInt(echannel.getAttribute("to"));k++){
								cmm[k] = cm;
							}
						}
					}
					mun.cmm = cmm;
					muns.add(mun);
				}
			}
			DmxMerger.mergedUniverses = muns;
			
			//End parsing MergedUniverses
		} catch (Exception e){
			e.printStackTrace();
		}
	}
	
	//TODO Add Exceptions
	@SuppressWarnings("static-access")
	public static void saveToDisk(String path){
		try {
			DocumentBuilderFactory docFactory = DocumentBuilderFactory.newInstance();
			DocumentBuilder docBuilder = docFactory.newDocumentBuilder();

			Document doc = docBuilder.newDocument();
			Element root = doc.createElement("configuration");
			doc.appendChild(root);
			
			/*
			 * Saving Cameraviews
			 */
			Element cameraviews = doc.createElement("cameraviews");
			root.appendChild(cameraviews);
			for(Iterator<Cameraview> it = views.getList().iterator();it.hasNext();){
				Cameraview view = it.next();
				Element camera = doc.createElement("camera");
				camera.setAttribute("id", String.valueOf(view.id));
				
				Element name = doc.createElement("name");
				name.appendChild(doc.createTextNode(view.name));
				
				Element source = doc.createElement("source");
				source.appendChild(doc.createTextNode(view.source));

				Element sourcetype = doc.createElement("sourcetype");
				sourcetype.appendChild(doc.createTextNode(view.sourcetype));
				
				Element resolution = doc.createElement("resolution");
				resolution.setAttribute("x", String.valueOf(view.res.width));
				resolution.setAttribute("y", String.valueOf(view.res.height));
				
				Element used = doc.createElement("used");
				used.appendChild(doc.createTextNode(String.valueOf(view.used)));
				
				camera.appendChild(name);
				camera.appendChild(source);
				camera.appendChild(sourcetype);
				camera.appendChild(resolution);
				camera.appendChild(used);
				cameraviews.appendChild(camera);
			}
			Element freeid = doc.createElement("freeid");
			freeid.appendChild(doc.createTextNode(String.valueOf(views.nextId)));
			cameraviews.appendChild(freeid);
			//End saving Cameraviews
			
			
			/*
			 * Save Universes
			 */
			Element universes = doc.createElement("universes");
			root.appendChild(universes);
			for(Iterator<Universe> it = DmxMerger.universes.iterator(); it.hasNext();){
				Universe un = it.next();
				Element universe = doc.createElement("universe");
				universe.setAttribute("id", String.valueOf(un.id));
				
				Element direction = doc.createElement("direction");
				Element name = doc.createElement("name");
				//Check Universe Type and parse it
				//Artnet
				if(un.getClass() == UniverseArtnet.class){
					UniverseArtnet una = (UniverseArtnet)un;
					universe.setAttribute("type", "artnet");
					
					name = doc.createElement("name");
					name.appendChild(doc.createTextNode(una.name));
					
					String dir = "IN";
					if(una.direction==Universe.OUT){
						dir = "OUT";
					}
					direction = doc.createElement("direction");
					direction.appendChild(doc.createTextNode(dir));
					
					Element subnet = doc.createElement("subnet");
					subnet.appendChild(doc.createTextNode(String.valueOf(una.SubnetUniverseId)));

					Element artnetuniverse = doc.createElement("universe");
					artnetuniverse.setTextContent(String.valueOf(una.ArtnetUniverseId));
					universe.appendChild(subnet);
					universe.appendChild(artnetuniverse);
				}
				//Other Type
				else{
					System.out.println("Could not detect type...");
				}
				
				universe.appendChild(name);
				universe.appendChild(direction);
				universes.appendChild(universe);
			}
			//End saving Universes
			
			/*
			 * Saving MergedUniverses
			 */

			Element muniverses = doc.createElement("muniverses");
			root.appendChild(muniverses);
			//Do this for all mergedUniverses
			for(Iterator<MergedUniverse> it = DmxMerger.mergedUniverses.iterator(); it.hasNext();){
				MergedUniverse mun = it.next();
				Element muniverse = doc.createElement("muniverse");
				//Setting global information: input, output...
				boolean first = true;
				String input = "";
				for(int nm : mun.input){
					if(first){
						input = input + String.valueOf(nm);
						first = false;
					}
					else{
						input = input + "," + String.valueOf(nm);
					}
				}
				muniverse.setAttribute("input", input);

				first = true;
				String output = "";
				for(int nm : mun.output){
					if(first){
						output = output + String.valueOf(nm);
						first = false;
					}
					else{
						output = output + "," + String.valueOf(nm);
					}
				}
				muniverse.setAttribute("output", output);
				Element channel = doc.createElement("channel");
				for(int i = 0; i < mun.cmm.length; i++ ){
					ChannelMergeMode cm = mun.cmm[i];
					if(i==0){
						boolean isfirst = true;
						String primary = "";
						Arrays.sort(cm.universes);
						for(int pr : cm.universes){
							if(isfirst){
								primary = String.valueOf(pr);
							}
							else{
								primary = primary + "," + String.valueOf(pr);
							}
						}
						channel.setAttribute("primary", primary);
						if(cm.type== ChannelMergeMode.LTP){
							channel.setAttribute("type", "LTP");
						}
						else{
							channel.setAttribute("type", "HTP");
						}
						channel.setAttribute("from", "0");
					}
					else if(i!=511){
						Arrays.sort(cm.universes);
						//is this element the same as the last one???
						Arrays.sort(mun.cmm[i].universes);
						if(cm.equals(mun.cmm[i-1])){
							//Do nothing, wait for next loop
						}
						else{
							//close the channel element and add it to the document
							channel.setAttribute("to", String.valueOf(i-1));
							muniverse.appendChild(channel.cloneNode(true));
							channel.setAttribute("from", String.valueOf(i));
							boolean isfirst = true;
							String primary = "";
							Arrays.sort(cm.universes);
							for(int pr : cm.universes){
								if(isfirst){
									primary = String.valueOf(pr);
									isfirst = false;
								}
								else{
									primary = primary + "," + String.valueOf(pr);
								}
							}
							channel.setAttribute("primary", primary);
							if(cm.type== ChannelMergeMode.LTP){
								channel.setAttribute("type", "LTP");
							}
							else{
								channel.setAttribute("type", "HTP");
							}
						}
					}
					else{
						channel.setAttribute("to", String.valueOf(i));
						muniverse.appendChild(channel.cloneNode(false));
					}
				}
				muniverses.appendChild(muniverse);
			}
			//End saving MergedUniverses
			
			/*
			 * Save the File to Disk
			 */
			TransformerFactory transformerFactory = TransformerFactory.newInstance();
			Transformer transformer = transformerFactory.newTransformer();
			DOMSource source = new DOMSource(doc);
			StreamResult result = new StreamResult(new File(path));
			transformer.transform(source, result);
			System.out.println("XML File saved");
		} catch (ParserConfigurationException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		} catch (TransformerConfigurationException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		} catch (TransformerException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
	}
	
	public static void saveToDisk(){
		saveToDisk(defPath);
	}
	public static Cameraviews getCameraviews(){
		return views;
	}
	public static void setCameraviews(Cameraviews views){
		Configuration.views = views;
	}
}
